using System;
using System.Diagnostics;
using Community.CsharpSqlite.Entity;

namespace Community.CsharpSqlite
{
    using MemJournal = sqlite3_file;

    public partial class Sqlite3
    {
        /*
    ** 2007 August 22
    **
    ** The author disclaims copyright to this source code.  In place of
    ** a legal notice, here is a blessing:
    **
    **    May you do good and not evil.
    **    May you find forgiveness for yourself and forgive others.
    **    May you share freely, never taking more than you give.
    **
    *************************************************************************
    **
    ** This file contains code use to implement an in-memory rollback journal.
    ** The in-memory rollback journal is used to journal transactions for
    ** ":memory:" databases and when the journal_mode=MEMORY pragma is used.
    *************************************************************************
    **  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
    **  C#-SQLite is an independent reimplementation of the SQLite software library
    **
    **  SQLITE_SOURCE_ID: 2009-12-07 16:39:13 1ed88e9d01e9eda5cbc622e7614277f29bcc551c
    **
    **  $Header: Community.CsharpSqlite/src/memjournal_c.cs,v bcbd36f24b23 2010/02/18 17:35:24 Noah $
    *************************************************************************
    */

        //#include "sqliteInt.h"

        /* Forward references to internal structures */
        //typedef struct MemJournal MemJournal;
        //typedef struct FilePoint FilePoint;
        //typedef struct FileChunk FileChunk;


        /* Macro to find the minimum of two numeric values.
    */
        //#if ! MIN
        //# define MIN(x,y) ((x)<(y)?(x):(y))
        //#endif
        private static readonly sqlite3_io_methods MemJournalMethods = new sqlite3_io_methods(
            1, /* iVersion */
            memjrnlClose, /* xClose */
            memjrnlRead, /* xRead */
            memjrnlWrite, /* xWrite */
            memjrnlTruncate, /* xTruncate */
            memjrnlSync, /* xSync */
            memjrnlFileSize, /* xFileSize */
            null, /* xLock */
            null, /* xUnlock */
            null, /* xCheckReservedLock */
            null, /* xFileControl */
            null, /* xSectorSize */
            null /* xDeviceCharacteristics */
            );

        //private static int MIN(int x, int y)
        //{
        //    return (x < y) ? x : y;
        //}

        //private static int MIN(int x, uint y)
        //{
        //    return (x < y) ? x : (int) y;
        //}


        /*
    ** Read data from the in-memory journal file.  This is the implementation
    ** of the sqlite3_vfs.xRead method.
    */

        private static int memjrnlRead(
            sqlite3_file pJfd, /* The journal file from which to read */
            byte[] zBuf, /* Put the results here */
            int iAmt, /* Number of bytes to read */
            long iOfst /* Begin reading at this offset */
            )
        {
            MemJournal p = pJfd;
            byte[] zOut = zBuf;
            int nRead = iAmt;
            int iChunkOffset;
            FileChunk pChunk;

            /* SQLite never tries to read past the end of a rollback journal file */
            Debug.Assert(iOfst + iAmt <= p.endpoint.iOffset);

            if (p.readpoint.iOffset != iOfst || iOfst == 0)
            {
                int iOff = 0;
                for (pChunk = p.pFirst;
                     UnitTest.ALWAYS(pChunk != null) && (iOff + Const.JOURNAL_CHUNKSIZE) <= iOfst;
                     pChunk = pChunk.pNext
                    )
                {
                    iOff += Const.JOURNAL_CHUNKSIZE;
                }
            }
            else
            {
                pChunk = p.readpoint.pChunk;
            }

            iChunkOffset = (int) (iOfst%Const.JOURNAL_CHUNKSIZE);
            int izOut = 0;
            do
            {
                int iSpace = Const.JOURNAL_CHUNKSIZE - iChunkOffset;
                int nCopy = Math.Min(nRead, (Const.JOURNAL_CHUNKSIZE - iChunkOffset));
                Buffer.BlockCopy(pChunk.zChunk, iChunkOffset, zOut, izOut, nCopy);
                    //memcpy( zOut, pChunk.zChunk[iChunkOffset], nCopy );
                izOut += nCopy; // zOut += nCopy;
                nRead -= iSpace;
                iChunkOffset = 0;
            } while (nRead >= 0 && (pChunk = pChunk.pNext) != null && nRead > 0);
            p.readpoint.iOffset = (int) (iOfst + iAmt);
            p.readpoint.pChunk = pChunk;

            return StatusCode.SQLITE_OK;
        }

        /*
    ** Write data to the file.
    */

        private static int memjrnlWrite(
            sqlite3_file pJfd, /* The journal file into which to write */
            byte[] zBuf, /* Take data to be written from here */
            int iAmt, /* Number of bytes to write */
            long iOfst /* Begin writing at this offset into the file */
            )
        {
            MemJournal p = pJfd;
            int nWrite = iAmt;
            byte[] zWrite = zBuf;
            int izWrite = 0;

            /* An in-memory journal file should only ever be appended to. Random
      ** access writes are not required by sqlite.
      */
            Debug.Assert(iOfst == p.endpoint.iOffset);
            Helper.UNUSED_PARAMETER(iOfst);

            while (nWrite > 0)
            {
                FileChunk pChunk = p.endpoint.pChunk;
                int iChunkOffset = (p.endpoint.iOffset%Const.JOURNAL_CHUNKSIZE);
                int iSpace = Math.Min(nWrite, Const.JOURNAL_CHUNKSIZE - iChunkOffset);

                if (iChunkOffset == 0)
                {
                    /* New chunk is required to extend the file. */
                    var pNew = new FileChunk(); // Malloc.sqlite3_malloc( sizeof( FileChunk ) );
                    if (null == pNew)
                    {
                        return ExtendedResultCode.SQLITE_IOERR_NOMEM;
                    }
                    pNew.pNext = null;
                    if (pChunk != null)
                    {
                        Debug.Assert(p.pFirst != null);
                        pChunk.pNext = pNew;
                    }
                    else
                    {
                        Debug.Assert(null == p.pFirst);
                        p.pFirst = pNew;
                    }
                    p.endpoint.pChunk = pNew;
                }

                Buffer.BlockCopy(zWrite, izWrite, p.endpoint.pChunk.zChunk, iChunkOffset, iSpace);
                    //memcpy( &p.endpoint.pChunk.zChunk[iChunkOffset], zWrite, iSpace );
                izWrite += iSpace; //zWrite += iSpace;
                nWrite -= iSpace;
                p.endpoint.iOffset += iSpace;
            }

            return StatusCode.SQLITE_OK;
        }

        /*
    ** Truncate the file.
    */

        private static int memjrnlTruncate(sqlite3_file pJfd, long size)
        {
            MemJournal p = pJfd;
            FileChunk pChunk;
            Debug.Assert(size == 0);
            Helper.UNUSED_PARAMETER(size);
            pChunk = p.pFirst;
            while (pChunk != null)
            {
                FileChunk pTmp = pChunk;
                pChunk = pChunk.pNext;
                //Malloc.sqlite3_free( ref pTmp );
            }
            sqlite3MemJournalOpen(pJfd);
            return StatusCode.SQLITE_OK;
        }

        /*
    ** Close the file.
    */

        private static int memjrnlClose(MemJournal pJfd)
        {
            memjrnlTruncate(pJfd, 0);
            return StatusCode.SQLITE_OK;
        }


        /*
    ** Sync the file.
    **
    ** Syncing an in-memory journal is a no-op.  And, in fact, this routine
    ** is never called in a working implementation.  This implementation
    ** exists purely as a contingency, in case some malfunction in some other
    ** part of SQLite causes Sync to be called by mistake.
    */

        private static int memjrnlSync(sqlite3_file NotUsed, int NotUsed2)
        {
            /*NO_TEST*/
            Helper.UNUSED_PARAMETER2(NotUsed, NotUsed2); /*NO_TEST*/
            Debug.Assert(false); /*NO_TEST*/
            return StatusCode.SQLITE_OK; /*NO_TEST*/
        }

        /*NO_TEST*/

        /*
    ** Query the size of the file in bytes.
    */

        private static int memjrnlFileSize(sqlite3_file pJfd, ref int pSize)
        {
            MemJournal p = pJfd;
            pSize = p.endpoint.iOffset;
            return StatusCode.SQLITE_OK;
        }

        /*
    ** Table of methods for MemJournal sqlite3_file object.
    */

        /*
    ** Open a journal file.
    */

        private static void sqlite3MemJournalOpen(sqlite3_file pJfd)
        {
            MemJournal p = pJfd;
            //memset( p, 0, sqlite3MemJournalSize() );
            p.pFirst = null;
            p.endpoint = new FilePoint();
            p.readpoint = new FilePoint();
            p.pMethods = MemJournalMethods;
        }

        /*
    ** Return true if the file-handle passed as an argument is
    ** an in-memory journal
    */

        private static bool sqlite3IsMemJournal(sqlite3_file pJfd)
        {
            return pJfd.pMethods == MemJournalMethods;
        }

        /*
    ** Return the number of bytes required to store a MemJournal that uses vfs
    ** pVfs to create the underlying on-disk files.
    */

        private static int sqlite3MemJournalSize()
        {
            return 3096; // sizeof( MemJournal );
        }
    }
}